package com.alone.mybatisplus.datascope.core.domain;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.alone.mybatisplus.datascope.conts.DataScopeModel;
import com.google.common.base.Joiner;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author Alone
 */
@Slf4j
@Getter
public class ModelDataScope extends DataScope {

    private static final String NO_PERMISSION = "SELECT * FROM (%s) data_scope WHERE FALSE";
    private static final String PROCESSING = "SELECT %s FROM (%s) data_scope WHERE %s";
    private static final String PROCESSING_IN_NODE = "data_scope.%s IN (%s)";
    private static final String PROCESSING_NOT_IN_NODE = "data_scope.%s NOT IN (%s)";

    public ModelDataScope(DataScopeModel model) {
        super(false);
        this.model = model;
    }

    private final DataScopeModel model;

    private final Map<String, Set<Object>> columnInValueMap = new HashMap<>(1);

    private final Map<String, Set<Object>> columnNotInValueMap = new HashMap<>(0);

    public static ModelDataScope of(DataScopeModel model) {
        return new ModelDataScope(model);
    }

    @Override
    public String getRenderedSql(String orgSql) {
        if (isAllowAll()) {
            return orgSql;
        }
        if (hasPermission()) {
            return doGetRenderedSql(orgSql);
        }
        return String.format(NO_PERMISSION, orgSql);
    }

    public void addInValue(String column, Collection<?> values) {
        Set<Object> valueSet = columnInValueMap.get(column);
        if (CollUtil.isEmpty(valueSet)) {
            valueSet = new HashSet<>();
            columnInValueMap.put(column, valueSet);
        }
        valueSet.addAll(values);
    }

    public void addNotInValue(String column, Collection<?> values) {
        Set<Object> valueSet = columnNotInValueMap.get(column);
        if (CollUtil.isEmpty(valueSet)) {
            valueSet = new HashSet<>();
            columnNotInValueMap.put(column, valueSet);
        }
        valueSet.addAll(values);
    }

    private boolean hasPermission() {
        return columnInValueMap.values().stream().anyMatch(CollUtil::isNotEmpty);
    }

    private String doGetRenderedSql(String orgSql) {
        String in = Joiner.on(" OR ")
                .join(columnInValueMap.keySet().stream()
                        .filter(key -> CollUtil.isEmpty(columnNotInValueMap.get(key)))
                        .map(key -> String.format(
                                PROCESSING_IN_NODE,
                                key,
                                Joiner.on(",").join(columnInValueMap.get(key)))
                        ).collect(Collectors.toList()));

        String notIn = Joiner.on(" AND ")
                .join(columnNotInValueMap.keySet().stream()
                        .filter(key -> CollUtil.isEmpty(columnNotInValueMap.get(key)))
                        .map(key -> String.format(
                                PROCESSING_NOT_IN_NODE,
                                key,
                                Joiner.on(",").join(columnNotInValueMap.get(key)))
                        ).collect(Collectors.toList()));

        String res;
        if (CharSequenceUtil.isNotBlank(in) && CharSequenceUtil.isNotBlank(notIn)) {
            res = in + " AND " + notIn;
        } else if (CharSequenceUtil.isNotBlank(in)) {
            res = in;
        } else {
            res = notIn;
        }

        String selectColumn = extractSelectColumn(orgSql);
        orgSql = orgSql.replace(selectColumn, " * ");
        return String.format(PROCESSING, selectColumn, orgSql, res);
    }


    private static final Pattern PATTERN = Pattern.compile("select(.*)from.*", Pattern.CASE_INSENSITIVE + Pattern.DOTALL);

    private String extractSelectColumn(String orgSql) {
        Matcher matcher = PATTERN.matcher(orgSql);
        if (matcher.matches()) {
            return matcher.group(1);
        }
        log.warn("can not handle orgSql:{}", orgSql);
        throw new IllegalArgumentException("can not handle orgSql");
    }

}
